#include "SkyNet.h"

#ifdef USE_DSP
const int parallel_factor = 1;
#else
const int parallel_factor = 2;
#endif
const int unroll_factor = 32 / parallel_factor;

void PWCONV1X1(
    hls::stream<ADT> IFM_stream[32],
    hls::stream<BDT> OFM_stream[32],
    const WDT WBUF1x1[32][32]
#ifdef USE_DSP
    ,
    bool ap_clk_div2
#endif
)
{
IFM_row_loop_w:
    for (int w = 0; w < 83; w++)
    {
    IFM_row_loop_h:
        for (int h = 0; h < 43; h++)
        {

#ifdef USE_DSP
#pragma HLS LOOP_FLATTEN
#pragma HLS PIPELINE II=1
            int ci = 0;
            ADT C1in_buf[16];
            ADT C2in_buf[16];
#else
        WBUF1x1_col_i_loop:
            for (int ci = 0; ci < parallel_factor; ci++)
            {
#pragma HLS LOOP_FLATTEN
#pragma HLS PIPELINE II=1
                ADT C1in_buf[16];
                ADT C2in_buf[16];
#endif
            WBUF1x1_col_j_loop:
                for (int cj = 0; cj < unroll_factor / 2; cj++)
                {
#pragma HLS UNROLL
                    // Fully unrolled inner loop, which is the pe array
                    const int c = ci * (unroll_factor / 2) + cj;

                    ap_int<48> Accum = ap_int<48>(0);

                IFM_col_loop:
                    for (int k = 0; k < 16; k++)
                    {
#pragma HLS UNROLL
                        if (c == 0)
                        {
                            C1in_buf[k] = IFM_stream[2 * k + 0].read();
                            C2in_buf[k] = IFM_stream[2 * k + 1].read();
                        }
                        const ADT C1in = C1in_buf[k];
                        const ADT C2in = C2in_buf[k];

                        const WDT A1in = WBUF1x1[2 * c + 0][2 * k + 0];
                        const WDT B1in = WBUF1x1[2 * c + 1][2 * k + 0];
                        const WDT A2in = WBUF1x1[2 * c + 0][2 * k + 1];
                        const WDT B2in = WBUF1x1[2 * c + 1][2 * k + 1];

#ifdef USE_DSP
                        ap_int<48> Result = __builtin_mac6x2_mac8x1(A1in, A2in, B1in, B2in, C1in, C2in, Accum, (k == 0), ap_clk_div2);
#else
                        ap_int<48> Result = ap_int<48>(((ap_int<27>(A1in) << 21) + ap_int<27>(B1in)) * C1in) + ap_int<48>(((ap_int<27>(A2in) << 21) + ap_int<27>(B2in)) * C2in) + ((k == 0) ? ap_int<48>(0) : Accum);
#endif

                        const ap_int<19> Result_1 = Result.range(39, 21) + (Result[18] & (!Accum[18]));
                        const ap_int<19> Result_2 = Result.range(18, 0);

                        if (h > 0 && h < 42 && w > 0 && w < 82)
                        {
                            Accum.range(47, 40) = ap_int<8>(0);
                            Accum.range(39, 21) = Result_1;
                            Accum.range(20, 19) = ap_int<2>(0);
                            Accum.range(18, 0) = Result_2;
                        }
                    }
                    const ap_int<19> Result_1 = Accum.range(39, 21);
                    const ap_int<19> Result_2 = Accum.range(18, 0);
                    OFM_stream[2 * c + 0].write(Result_1 > smax ? BDT(smax) : Result_1 < smin ? BDT(smin) : BDT(Result_1));
                    OFM_stream[2 * c + 1].write(Result_2 > smax ? BDT(smax) : Result_2 < smin ? BDT(smin) : BDT(Result_2));
                }
#ifndef USE_DSP
            }
#endif
        }
    }
}